import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.lang.Math.toIntExact;

public class Time {
    private long timeStamp;
    private int Y, M, D, h, m, s;

    Time(long timeStamp, String Date) {
        if (null == Date || 0 > timeStamp || 253402271999L < timeStamp || !this.processDate(Date)) {
            System.out.print("Invalid input parameter.\n");
            System.exit(1);
        }
        this.timeStamp = timeStamp;
    }

    private boolean processDate(String Date) {
        int flag;
        int Y = 0, M = 0, D = 0, h = 0, m = 0, s = 0;
        String regEx1 = "^\\d{4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}$";

        Pattern pattern1 = Pattern.compile(regEx1);
        Matcher matcher1 = pattern1.matcher(Date);
        if (matcher1.matches()) {
            String[] tmpDA1 = Date.split("\\s+");
            String[] tmpDA2 = tmpDA1[0].split("-");
            String[] tmpDA3 = tmpDA1[1].split(":");
            Y = Integer.parseInt(tmpDA2[0]);
            M = Integer.parseInt(tmpDA2[1]);
            D = Integer.parseInt(tmpDA2[2]);
            h = Integer.parseInt(tmpDA3[0]);
            m = Integer.parseInt(tmpDA3[1]);
            s = Integer.parseInt(tmpDA3[2]);
            if (1970 > Y || 9999 < Y || 1 > M || 12 < M || 1 > D || 31 < D || 0 > h || 23 < h || 0 > m || 59 < m || 0 > s || 59 < s) {
                flag = 0;
            } else {
                if (!(isLeapYear(Y)) && 2 == M && 28 < D) {
                    flag = 0;
                } else if ((isLeapYear(Y)) && 2 == M && 29 < D) {
                    flag = 0;
                } else if ((4 == M || 6 == M || 9 == M || 11 == M) && 30 < D) {
                    flag = 0;
                } else {
                    flag = 1;
                }
            }
        } else {
            flag = 0;
        }

        if (0 != flag) {
            this.Y = Y;
            this.M = M;
            this.D = D;
            this.h = h;
            this.m = m;
            this.s = s;
            return true;
        } else {
            return false;
        }
    }

    public void toDate() {
        long sec, day;
        int Y, M, D, h, m, s;
        long timeStamp;

        timeStamp = this.timeStamp + 28800L;//UTC to CST

        sec = timeStamp % 86400L;
        h = toIntExact(sec / 3600L);

        sec = sec % 3600L;
        m = toIntExact(sec / 60L);

        s = toIntExact(sec % 60L);

        day = timeStamp / 86400L;
        for (Y = 1970; day > 0; Y++) {
            D = 365;
            if (isLeapYear(Y)) {
                D++;
            }
            if (day >= D) {
                day = day - D;
            } else {
                break;
            }
        }

        for (M = 1; M < 12; M++) {
            D = getMonthDays(M, Y);

            if (day >= D) {
                day = day - D;
            } else {
                break;
            }
        }

        D = toIntExact(day + 1L);
        System.out.print("Convert timestamp:" + this.timeStamp + " to date:" + Y + "-" + M + "-" + D + " " + h + ":" + m + ":" + s + " " + getWeekday(Y, M, D) + " CCT +08:00 \n");
    }


    public long toTS(boolean flag) {
        int i, dayCount = 0;
        long timeStamp;
        for (i = 1970; i < Y; i++) {
            dayCount = dayCount + 365;
            if (isLeapYear(i)) {
                dayCount++;
            }
        }

        for (i = 1; i < M; i++) {
            dayCount = dayCount + getMonthDays(i, Y);
        }

        dayCount = dayCount + D - 1;
        timeStamp = (long) dayCount * 86400L + (long) h * 3600L + (long) m * 60L + (long) s;//CST timestamp
        timeStamp = timeStamp - 28800L;//CST to UTC

        if (flag) {
            System.out.print("Convert date:" + Y + "-" + M + "-" + D + " " + h + ":" + m + ":" + s + " to timestamp:" + timeStamp + "\n");
        }
        return timeStamp;
    }

    public long toTS() {
        return toTS(true);
    }

    public void getTimeDifference() {
        boolean flag;
        int timeDifference, h, s, m;
        long secCount, sec;
        long DateTS = toTS(false);

        if (DateTS >= this.timeStamp) {
            secCount = DateTS - this.timeStamp;
            flag = true;
        } else {
            secCount = this.timeStamp - DateTS;
            flag = false;
        }

        timeDifference = toIntExact(secCount / 86400L);
        sec = secCount % 86400L;
        h = toIntExact(sec / 3600L);

        sec = sec % 3600L;
        m = toIntExact(sec / 60L);

        s = toIntExact(sec % 60L);

        if (flag) {
            System.out.print("Date is " + timeDifference + " day(s) " + h + " hour(s) " + m + " minute(s) " + s + " second(s) faster than Timestamp.\n");
        } else {
            System.out.print("Timestamp is " + timeDifference + " day(s) " + h + " hour(s) " + m + " minute(s) " + s + " second(s) faster than Date.\n");
        }
    }

    public void getDate() {
        System.out.print("Date:" + Y + "-" + M + "-" + D + " " + h + ":" + m + ":" + s + " " + getWeekday(Y, M, D) + " CCT +08:00 \n");
    }

    public void getTS() {
        System.out.print("TimeStamp:" + timeStamp + "\n");
    }

    public void setDate(String Date) {
        if (this.processDate(Date)) {
            System.out.print("Set date: " + Date + " done!\n");
        } else {
            System.out.print("Set date failed,Invalid input parameter.\n");
        }
    }

    public void setTS(long timeStamp) {
        if (0 > timeStamp || 253402271999L < timeStamp) {
            System.out.print("Set timestamp failed,Invalid input parameter.\n");
        } else {
            this.timeStamp = timeStamp;
            System.out.print("Set timestamp: " + timeStamp + " done!\n");
        }
    }

    private boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0 || year % 400 == 0);
    }

    private int getMonthDays(int M, int Y) {
        if (0 > M || 12 < M) {
            return 0;
        }
        if (4 == M || 6 == M || 9 == M || 11 == M) {
            return 30;
        } else if (2 == M) {
            if (isLeapYear(Y)) {
                return 29;
            } else {
                return 28;
            }
        } else {
            return 31;
        }
    }

    private String getWeekday(int year, int month, int day) {
        String weekday = "";
        if (month == 1) {
            month = 13;
            year--;
        }
        if (month == 2) {
            month = 14;
            year--;
        }
        int q = day;
        int m = month;
        int k = year % 100;
        int j = year / 100;
        int h = q + 13 * (m + 1) / 5 + k + k / 4 + j / 4 + 5 * j;
        h = h % 7;
        switch (h) {
            case 0:
                weekday = "Sat.";
                break;
            case 1:
                weekday = "Sun.";
                break;
            case 2:
                weekday = "Mon.";
                break;
            case 3:
                weekday = "Tues.";
                break;
            case 4:
                weekday = "Wed.";
                break;
            case 5:
                weekday = "Thur.";
                break;
            case 6:
                weekday = "Fri.";
                break;
        }
        return weekday;
    }
}
